{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Materials\n",
    "\n",
    "PyNE `Material` objects provide a way of representing, manipulating, and storing materials. A `Material` object is a collection of nuclides with various mass fractions (though methods for converting to/from atom fractions are present as well). Optionally, a `Material` object may have an associated mass. By keeping the mass and the composition separate, operations that only affect one attribute may be performed independent of the other. Most of the functionality of the `Material` class is\n",
    "implemented in a C++, so this interface is very fast and light-weight.\n",
    "\n",
    "A `Material` may be initialized in a number of different ways.  For example, initializing from\n",
    "dictionaries of compositions are shown below. First import the `Material` class:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 567,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pyne.material import Material"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now create a low enriched uranium (leu) with a mass of 42:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 568,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Material:\n",
      "mass = 42.0\n",
      "density = -1.0\n",
      "atoms per molecule = -1.0\n",
      "-------------------------\n",
      "U235   0.04\n",
      "U238   0.96\n"
     ]
    }
   ],
   "source": [
    "# Low enriched uranium with mass 42\n",
    "leu = Material({'U238': 0.96, 'U235': 0.04}, 42)\n",
    "print(leu)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Create another `Material`, this one with 9 components. Notice that the mass is 9 x 1.0 = 9.0: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 569,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Material:\n",
      "mass = 9.0\n",
      "density = -1.0\n",
      "atoms per molecule = -1.0\n",
      "-------------------------\n",
      "H1     0.1111111111111111\n",
      "O16    0.1111111111111111\n",
      "Tm169  0.1111111111111111\n",
      "U235   0.1111111111111111\n",
      "U238   0.1111111111111111\n",
      "Pu239  0.1111111111111111\n",
      "Pu241  0.1111111111111111\n",
      "Am242  0.1111111111111111\n",
      "Cm244  0.1111111111111111\n"
     ]
    }
   ],
   "source": [
    "# Material with 9 components\n",
    "nucvec = {10010:  1.0, 80160:  1.0, 691690: 1.0, 922350: 1.0,\n",
    "          922380: 1.0, 942390: 1.0, 942410: 1.0, 952420: 1.0,\n",
    "          962440: 1.0}\n",
    "mat = Material(nucvec)\n",
    "print(mat)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Materials may also be initialized from plain text or HDF5 files (see ``Material.from_text()`` and\n",
    "``Material.from_hdf5()``)."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "------\n",
    "\n",
    "## Normalization\n",
    "\n",
    "When creating a new instance of the `Material` class, the mass fractions that define it are already normalized. However, you can retrieve the unnormalized mass vector using the ``Material.mult_by_mass()`` method. If you need to normalize the mass or composition, you can use the provided normalization routines: ``Material.normalize()`` or ``Material.norm_comp()``.\n",
    "\n",
    "For example, let's consider a scenario where we have 42 units of low-enriched uranium (LEU), consisting of 1.68 units of U-235 and 40.32 units of U-238."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 570,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{922350000: 1.68, 922380000: 40.32}"
      ]
     },
     "execution_count": 570,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "leu.mult_by_mass()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Recall that `mat` has a mass of 9. Here it is normalized to a mass of 1:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 571,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Material:\n",
      "mass = 1.0\n",
      "density = -1.0\n",
      "atoms per molecule = -1.0\n",
      "-------------------------\n",
      "H1     0.1111111111111111\n",
      "O16    0.1111111111111111\n",
      "Tm169  0.1111111111111111\n",
      "U235   0.1111111111111111\n",
      "U238   0.1111111111111111\n",
      "Pu239  0.1111111111111111\n",
      "Pu241  0.1111111111111111\n",
      "Am242  0.1111111111111111\n",
      "Cm244  0.1111111111111111\n"
     ]
    }
   ],
   "source": [
    "# Normalize\n",
    "mat.normalize()\n",
    "print(mat)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 572,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1.0"
      ]
     },
     "execution_count": 572,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Check mass\n",
    "mat.mass"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "-----------\n",
    "\n",
    "## Material Arithmetic\n",
    "\n",
    "The `Material` class also provides support for arithmetic operations with numeric types. When you add two instances of `Material` together, a new `Material` object is returned. The values of this new object are the weighted union of the two original materials.\n",
    "\n",
    "On the other hand, multiplying a `Material` by a numeric type, such as 2, will result in the mass of the original material being doubled. The composition of the material remains unchanged."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 573,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Material:\n",
      "mass = 2.0\n",
      "density = -1.0\n",
      "atoms per molecule = -1.0\n",
      "-------------------------\n",
      "H1     0.11111111111111108\n",
      "O16    0.11111111111111108\n",
      "Tm169  0.11111111111111108\n",
      "U235   0.11111111111111108\n",
      "U238   0.11111111111111108\n",
      "Pu239  0.11111111111111108\n",
      "Pu241  0.11111111111111108\n",
      "Am242  0.11111111111111108\n",
      "Cm244  0.11111111111111108\n"
     ]
    }
   ],
   "source": [
    "# Multiply by 2\n",
    "other_mat = mat * 2\n",
    "print(other_mat)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 574,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "2.0"
      ]
     },
     "execution_count": 574,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Mass\n",
    "other_mat.mass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 575,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Material:\n",
      "mass = 60.0\n",
      "density = -1.0\n",
      "atoms per molecule = -1.0\n",
      "-------------------------\n",
      "H1     0.03333333333333332\n",
      "O16    0.03333333333333332\n",
      "Tm169  0.03333333333333332\n",
      "U235   0.06133333333333332\n",
      "U238   0.7053333333333334\n",
      "Pu239  0.03333333333333332\n",
      "Pu241  0.03333333333333332\n",
      "Am242  0.03333333333333332\n",
      "Cm244  0.03333333333333332\n"
     ]
    }
   ],
   "source": [
    "# Add leu to mat multiply by 18\n",
    "weird_mat = leu + mat * 18\n",
    "print(weird_mat)\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Note:** There are also ways of mixing `Materials` by volume using known densities. See the `pyne.MultiMaterial` class for more information."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---------------\n",
    "\n",
    "## Raw Member Access\n",
    "\n",
    "You may also change the attributes of a material directly without generating a new \n",
    "material instance."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 576,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Material:\n",
      "mass = 10.0\n",
      "density = -1.0\n",
      "atoms per molecule = -1.0\n",
      "-------------------------\n",
      "H2     3.0\n",
      "U235   15.0\n"
     ]
    }
   ],
   "source": [
    "# Update mass\n",
    "other_mat.mass = 10\n",
    "\n",
    "# Update material composition\n",
    "other_mat.comp = {10020000: 3, 922350000: 15.0}\n",
    "\n",
    "print(other_mat)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Of course when you do this you have to be careful because the composition and mass may now be out\n",
    "of sync.  This may always be fixed with normalization."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 577,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Material:\n",
      "mass = 10.0\n",
      "density = -1.0\n",
      "atoms per molecule = -1.0\n",
      "-------------------------\n",
      "H2     0.16666666666666666\n",
      "U235   0.8333333333333334\n"
     ]
    }
   ],
   "source": [
    "# Normalize composition\n",
    "other_mat.norm_comp()\n",
    "print(other_mat)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "--------\n",
    "\n",
    "## Indexing & Slicing\n",
    "\n",
    "In addition, the `Material` class provides powerful indexing and slicing capabilities for accessing and manipulating sub-materials within the material or its composition.\n",
    "\n",
    "- When indexing into the composition, you can only use integer keys to retrieve the normalized value of a specific component. This allows you to retrieve the composition fraction of a specific nuclide.\n",
    "\n",
    "- When indexing into the material, you have more flexibility and can perform a wide range of operations. It returns the unnormalized mass weight of the specified sub-material. You can use integer keys, string keys, slices, or sequences of nuclides to index into the material and perform operations on specific parts of the material."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 578,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.04"
      ]
     },
     "execution_count": 578,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Check composition of low enriched uranium\n",
    "leu.comp[922350000]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 579,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1.68"
      ]
     },
     "execution_count": 579,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Check unnormalized mass of U235\n",
    "leu['U235']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 580,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Material:\n",
      "mass = 50.0\n",
      "density = -1.0\n",
      "atoms per molecule = -1.0\n",
      "-------------------------\n",
      "U235   0.07359999999999998\n",
      "U238   0.8464\n",
      "Pu239  0.03999999999999998\n",
      "Pu241  0.03999999999999998\n"
     ]
    }
   ],
   "source": [
    "# Slice from U to Pu\n",
    "print(weird_mat['U':'Am'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 581,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Material:\n",
      "mass = 50.333333333333336\n",
      "density = -1.0\n",
      "atoms per molecule = -1.0\n",
      "-------------------------\n",
      "H2     0.8344370860927152\n",
      "U235   0.16556291390728478\n"
     ]
    }
   ],
   "source": [
    "# Setting mass for H2 42\n",
    "other_mat[:920000000] = 42\n",
    "print(other_mat)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 582,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Material:\n",
      "mass = 0.6666666666666667\n",
      "density = -1.0\n",
      "atoms per molecule = -1.0\n",
      "-------------------------\n",
      "H1     0.16666666666666663\n",
      "U235   0.16666666666666663\n",
      "U238   0.16666666666666663\n",
      "Pu239  0.16666666666666663\n",
      "Pu241  0.16666666666666663\n",
      "Am242  0.16666666666666663\n"
     ]
    }
   ],
   "source": [
    "# Remove specific element\n",
    "del mat[962440, 'TM169', 'Zr90', 80160]\n",
    "print(mat)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Other methods also exist for obtaining commonly used sub-materials, such as gathering the Uranium or \n",
    "Plutonium vector.  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Molecular Weights & Atom Fractions\n",
    "\n",
    "You may also calculate the molecular weight of a material via the ``Material.molecular_weight`` method.\n",
    "This uses the ``pyne.data.atomic_mass`` function to look up the atomic mass values of\n",
    "the constituent nuclides."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 583,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "237.9290363047951"
      ]
     },
     "execution_count": 583,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Check molecule mass of low enriched uranium\n",
    "leu.molecular_mass()"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Please note that the default assumption is that materials have one atom per molecule, which may not be accurate for more complex substances. For instance, water consists of multiple atoms per molecule. If the number of atoms per molecule is not specified, the calculation of molecular weight will be incorrect by a factor of 3. To address this, it is necessary to provide the accurate number when using the method. If no valid number of molecules is stored for the material, this will assign the correct attribute to the class."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 584,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "6.003521561386799"
      ]
     },
     "execution_count": 584,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# New H2O material\n",
    "h2o = Material({'H1': 0.11191487328808077, 'O16': 0.8880851267119192})\n",
    "\n",
    "# Check molecule mass of H2O\n",
    "h2o.molecular_mass()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 585,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Material:\n",
      "mass = 1.0\n",
      "density = -1.0\n",
      "atoms per molecule = 3.0\n",
      "------------------------\n",
      "H1     0.11191487328808077\n",
      "O16    0.8880851267119192\n"
     ]
    }
   ],
   "source": [
    "# Set molecule mass of H2O to 3\n",
    "h2o.molecular_mass(3.0)\n",
    "print(h2o)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It is valuable to have the capability to convert the current mass-weighted material into an atom fraction mapping using the `Material.to_atom_frac()` method. For instance, in the case of water, when the number of atoms per molecule is accurately specified, the atom fraction returned will be normalized accordingly. On the other hand, if the atoms per molecule are set to their default value in the class, a fractional number of atoms will be returned. This functionality allows for flexible and precise representation of atom fractions in the material."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 586,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{10010000: 1.9999999999946356, 80160000: 1.0000000000053646}"
      ]
     },
     "execution_count": 586,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "h2o.to_atom_frac()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 587,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{10010000: 0.6666666666648785, 80160000: 0.3333333333351215}"
      ]
     },
     "execution_count": 587,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Set number of atoms per molecule of H2O to -1\n",
    "h2o.atoms_per_molecule = -1.0\n",
    "h2o.to_atom_frac()"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Moreover, it may be necessary to convert a given set of atom fractions into a new material stream. This can be achieved using the `Material.from_atom_frac()` method, which will clear the existing composition of the material and replace it with the mass-weighted values based on the atom fractions provided. It is important to note that when initializing a material from atom fractions, the sum of all atom fractions will be stored as the atoms per molecule attribute in this class. Additionally, if a mass is not already specified for the material, the molecular weight will be utilized for the conversion process."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 588,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{10010000: 0.11191487328888054, 80160000: 0.8880851267111195}\n",
      "Material:\n",
      "mass = 18.01056468408\n",
      "density = -1.0\n",
      "atoms per molecule = 3.0\n",
      "------------------------\n",
      "H1     0.11191487328888054\n",
      "O16    0.8880851267111195\n"
     ]
    }
   ],
   "source": [
    "h2o_atoms = {10010000: 2.0, 'O16': 1.0}\n",
    "h2o = Material()\n",
    "h2o.from_atom_frac(h2o_atoms)\n",
    "\n",
    "print(h2o.comp)\n",
    "print(h2o)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Furthermore, it is also possible to create a new material from atom fractions using other materials as references. This scenario often occurs in reactors, where the fuel vector is combined within another chemical form. Here is an example of obtaining a uranium oxide material by specifying the atom fractions of oxygen and low-enriched uranium:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 589,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Material:\n",
      "mass = 269.9188655439951\n",
      "density = -1.0\n",
      "atoms per molecule = 3.0\n",
      "------------------------\n",
      "O16    0.11851646299241672\n",
      "U235   0.03525934148030333\n",
      "U238   0.84622419552728\n"
     ]
    }
   ],
   "source": [
    "# New UO2 material\n",
    "uox = Material()\n",
    "\n",
    "# Add low enriched uranium as reference with O16\n",
    "uox.from_atom_frac({leu: 1.0, 'O16': 2.0})\n",
    "\n",
    "print(uox)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**NOTE:** Materials may be used as keys in a dictionary because they are hashable."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### User-defined Metadata\n",
    "\n",
    "Another important feature of materials is the availability of a `metadata` attribute, which allows users to store customized information about the material. This attribute serves as an in-memory JSON object attached to the underlying C++ class. As a result, the content that can be stored in the `metadata` is subject to the same limitations as JSON. Typically, the top-level of the `metadata` is expected to be a dictionary, although this requirement is not strictly enforced. Users can utilize the `metadata` attribute to store various details such as units, comments, provenance information, or any other relevant information they deem necessary for their specific use case."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 590,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "pyne.material.Material({922350000: 0.05, 922380000: 0.95}, 15.0, -1.0, -1.0, {\"units\":\"kg\"})"
      ]
     },
     "execution_count": 590,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Add metadata units\n",
    "leu = Material({922350: 0.05, 922380: 0.95}, 15, metadata={'units': 'kg'})\n",
    "leu"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 591,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Material:\n",
      "mass = 15.0\n",
      "density = -1.0\n",
      "atoms per molecule = -1.0\n",
      "units = kg\n",
      "-------------------------\n",
      "U235   0.05\n",
      "U238   0.95\n"
     ]
    }
   ],
   "source": [
    "print(leu)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 592,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{\"units\":\"kg\"}"
      ]
     },
     "execution_count": 592,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "leu.metadata"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 593,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{\"comments\":[\"Anthony made this material.\",\"And then Katy made it better!\"],\"id\":42,\"units\":\"kg\"}"
      ]
     },
     "execution_count": 593,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Add more metadata\n",
    "m = leu.metadata\n",
    "m['comments'] = ['Anthony made this material.']\n",
    "leu.metadata['comments'].append('And then Katy made it better!')\n",
    "m['id'] = 42\n",
    "leu.metadata"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 594,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{\"units\":\"solar mass\"}"
      ]
     },
     "execution_count": 594,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Update a specific metadata\n",
    "leu.metadata = {'units': 'solar mass'}\n",
    "leu.metadata"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This also manipulate other variables."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 595,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{\"units\":\"solar mass\"}"
      ]
     },
     "execution_count": 595,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 596,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'not solar masses'"
      ]
     },
     "execution_count": 596,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "leu.metadata['units'] = 'not solar masses'\n",
    "leu.metadata['units']"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As demonstrated above, the attrs interface allows direct access to the underlying JSON object, enabling users to manipulate it directly or by assigning it to another variable. Furthermore, it is possible to replace the `metadata` object with a new object of the appropriate type. It's important to note that when the `metadata` object is replaced, any previous views or references to the old object become invalid. Therefore, care should be taken when modifying or replacing the `metadata` object to avoid unintended consequences."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
